#include "Effect.h"
#include "Effect.h"
#include "Effect.h"
#include "Effect.h"
#include "ogl/phong/Effect.h"

Ogl::Phong::Effect::Effect()
    :Ogl::Gut::Effect3d(Ogl::Phong::Effect::GetFragMain())
{
    //std::cout << GetFragCode() << std::endl;
    m_MatBuffer = std::make_shared<Ogl::Gut::UniformBuffer>(sizeof(Material));
    m_lightBuffer = std::make_shared<Ogl::Gut::UniformBuffer>(sizeof(LightEffect));
}

void Ogl::Phong::Effect::Begin()
{
 
    Ogl::Gut::Effect3d::Begin();
    m_lightBuffer->Binding(3);
    m_MatBuffer->Binding(4);
    
    m_Shader->Use();
    m_Shader->Bind("LightBuffer", 3);
    m_Shader->Bind("MatBuffer", 4);

    m_AnimShader->Use();
    m_AnimShader->Bind("LightBuffer", 3);
    m_AnimShader->Bind("MatBuffer", 4);

}




void Ogl::Phong::Effect::SetLightEffect(const Ogl::Phong::LightEffect& lightEffect)
{
    m_lightBuffer->Upload<LightEffect>(lightEffect);

}



void Ogl::Phong::Effect::RenderMesh(const Ogl::Gut::Mesh* mesh, const Ogl::Math::Transform& transform, const Ogl::Phong::Material& material)
{

    m_Shader->Use();
    m_MatBuffer->Upload<Material>(material);
    Ogl::Gut::Effect3d::RenderMesh(mesh, transform);
}

void Ogl::Phong::Effect::RenderSkinnedMesh(const Ogl::Gut::Mesh* mesh, const Ogl::Math::Transform& transform, const Ogl::Gut::Effect3d::BoneData& boneData, const Ogl::Phong::Material& material)
{

    m_AnimShader->Use();
    m_MatBuffer->Upload<Material>(material);
    m_BoneBuffer->Upload<BoneData>(boneData);

    Ogl::Gut::Effect3d::RenderSkinnedMesh(mesh, transform, boneData);
}

void Ogl::Phong::Effect::End()
{
    Ogl::Gut::Shader::UnUse();
}

std::string Ogl::Phong::Effect::GetFragMain()
{
    return R"(

struct Material {
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
}; 

struct DirLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 direction;
    float pad;

};

struct PointLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 position;
    float pad3;

    float constant;
    float linear;
    float quadratic;
    float pad4;
};

struct SpotLight {
    vec3 ambient;
    float pad0;

    vec3 diffuse;
    float pad1;

    vec3 specular;
    float pad2;

    vec3 position;
    float cutOff;
    vec3 direction;
    float outerCutOff;
  
    float constant;
    float linear;
    float quadratic;
    float pad;

   
};




#define MAX_DIRECTION_NUM 8
#define MAX_POINT_NUM 8
#define MAX_SPOT_NUM 8


struct LightEffect {

    DirLight dirLights[MAX_DIRECTION_NUM];
    PointLight pointLights[MAX_POINT_NUM];
    SpotLight spotLights[MAX_SPOT_NUM];

    int numDirs;
    int numPoints ;
    int numSpots;
    int pad;
};


layout(std140) uniform LightBuffer
{
    LightEffect lightEffect;
};

layout(std140) uniform MatBuffer
{
    Material material;
};

// function prototypes
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);

out vec4 FragColor;

void main()
{    
    // properties
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);
    
    vec3 result=vec3(0.f,0.f,0.f);

    // dirLights
    for(int i = 0; i != lightEffect.numDirs; i++)
        result += CalcDirLight(lightEffect.dirLights[i], norm, viewDir);
    // pointlights
    for(int i = 0; i != lightEffect.numPoints; i++)
        result += CalcPointLight(lightEffect.pointLights[i], norm, FragPos, viewDir);    
    // spotlights
    for(int i = 0; i != lightEffect.numSpots; i++)
        result += CalcSpotLight(lightEffect.spotLights[i], norm, FragPos, viewDir);    
    
    FragColor = vec4(result, 1.0);
}

// calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
    vec3 lightDir = normalize(-light.direction);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // combine results
    //vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    //vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    //vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular * spec * material.specular.w;
    return (ambient + diffuse + specular);
}

// calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // combine results
    //vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    //vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    //vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular * spec * material.specular.w;

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
}

// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.specular.w);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // spotlight intensity
    float theta = dot(lightDir, normalize(-light.direction)); 
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
    // combine results
    // vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    // vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    // vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular * spec * material.specular.w;

    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}

)";
}
